Skip to content

Fast-path framework bookkeeping fields in state attribute access#6757

Open
Alek99 wants to merge 3 commits into
agent/event-loop-performance-benchmarksfrom
claude/reflex-perf-optimizations-01l7a3-eng-10096
Open

Fast-path framework bookkeeping fields in state attribute access#6757
Alek99 wants to merge 3 commits into
agent/event-loop-performance-benchmarksfrom
claude/reflex-perf-optimizations-01l7a3-eng-10096

Conversation

@Alek99

@Alek99 Alek99 commented Jul 14, 2026

Copy link
Copy Markdown
Member

Linear: ENG-10096

Description

_get_attribute was ~43% of profiled event-handling time, and much of that was the framework's own reads of dirty_vars, parent_state, substates, and _backend_vars paying the full inherited-vars/event-handler/proxying chain on every access from _mark_dirty/_clean/get_delta.

  • Add those names (plus dirty_substates) to the CLASS_VAR_NAMES fast path. They are never state vars, never inherited, and were never proxied (not in base_vars/backend_vars), so resolution via object.__getattribute__ is behavior-identical; assignments already ended in object.__setattr__ with no dirty-marking.
  • Reorder the proxying condition so base_vars/backend_vars membership is checked before is_mutable_type, sparing non-var values the type check.
  • Cache get_skip_vars() per class as a frozenset, invalidated when inherited_vars changes (_update_substate_inherited_vars, add_var).
  • Cache the frontend computed var names used by get_delta per class, invalidated when computed_vars changes (_evaluate, dynamic route vars).

Benchmarks (GitHub Actions runner, run, 2 passes each)

Case main this PR speedup
4 bookkeeping attr reads 3.66 / 3.56 us 0.61 / 0.66 us ~5.7x
setattr + get_delta + _clean cycle 84.4 / 71.4 us 54.5 / 51.4 us ~1.4x
get_skip_vars() 0.49 / 0.48 us 0.15 / 0.15 us ~3.3x

cProfile (5000 bookkeeping reads + 500 event cycles): _get_attribute tottime drops 0.120s -> 0.061s; total function calls drop 256k -> 207k.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
    • test_get_skip_vars_cached_per_class: per-class caching with inherited vars included and no cross-class leakage.
    • test_frontend_computed_var_names_cached_per_class: backend computed vars excluded; cached identity.
    • test_framework_bookkeeping_fields_not_proxied: bookkeeping containers stay raw while real state vars still get proxied.
  • Have you successfully ran tests with your changes locally?

🤖 Generated with Claude Code

https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X


Generated by Claude Code

@Alek99
Alek99 requested a review from a team as a code owner July 14, 2026 00:35
@linear-code

linear-code Bot commented Jul 14, 2026

Copy link
Copy Markdown

ENG-10096

@codspeed-hq

codspeed-hq Bot commented Jul 14, 2026

Copy link
Copy Markdown

Merging this PR will regress 2 benchmarks

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 45 improved benchmarks
❌ 2 regressed benchmarks
✅ 41 untouched benchmarks
🆕 1 new benchmark
⏩ 8 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_var_access[non_mutable_scalar] 57.6 ms 66.2 ms -12.99%
test_route_argument_extraction 56.8 µs 60.1 µs -5.37%
test_route_matching[1000] 1,653.1 µs 24.9 µs ×66
test_route_matching[100] 186.4 µs 24.9 µs ×7.5
test_var_access[mutable_list] 71.3 ms 19.1 ms ×3.7
test_state_delta_scalar_mutation[10000] 71.3 ms 19.5 ms ×3.7
test_dirty_computed_var_propagation[100] 22.1 ms 6.4 ms ×3.4
test_state_delta_scalar_mutation[1000] 7.8 ms 2.4 ms ×3.2
test_resolved_delta 7.9 ms 2.5 ms ×3.1
test_var_access[mutable_dict] 88.3 ms 32.5 ms ×2.7
test_dirty_computed_var_propagation[10] 2,548.7 µs 981.7 µs ×2.6
test_event_payload_plain_collection 68.7 ms 29.9 ms ×2.3
test_proxy_mapping_mutation 9.8 ms 4.4 ms ×2.2
test_state_tree_delta[three_by_three] 3.9 ms 1.8 ms ×2.2
test_state_tree_delta[width_10] 3.3 ms 1.5 ms ×2.2
test_route_matching[10] 55.2 µs 26.2 µs ×2.1
test_state_delta_scalar_mutation[100] 1,471.9 µs 699.9 µs ×2.1
test_state_tree_delta[depth_10] 4 ms 1.9 ms ×2.1
test_proxy_list_mutation 11.2 ms 5.9 ms +91.12%
test_var_arithmetic_chain 874.2 µs 467.8 µs +86.87%
... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/reflex-perf-optimizations-01l7a3-eng-10096 (1ded8bd) with agent/event-loop-performance-benchmarks (8745f9e)2

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on agent/event-loop-performance-benchmarks (fbf0b27) during the generation of this report, so 78c4daa was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR speeds up framework state handling and adds broader performance coverage. The main changes are:

  • Fast-path access for internal state bookkeeping fields.
  • Cached state serialization and computed-variable metadata.
  • Reduced mutable-proxy and dirty-propagation overhead.
  • Deferred chained-event routing outside the state lock.
  • Faster memo compilation and Redis state persistence.
  • Expanded benchmark and performance-test coverage.

Confidence Score: 5/5

No additional blocking issue qualifies for this review round.

  • No new distinct production failure was identified in the reviewed follow-up changes.

Important Files Changed

Filename Overview
reflex/state.py Adds fast attribute access, cached state metadata, mutable-proxy reuse, and optimized dirty propagation.
packages/reflex-base/src/reflex_base/vars/base.py Optimizes variable formatting, computed-variable validation, and recomputation tracking.
packages/reflex-base/src/reflex_base/event/processor/base_state_processor.py Moves final chained-event routing outside the state lock while keeping delta emission inline.
packages/reflex-base/src/reflex_base/components/memo.py Avoids rebuilding duplicate memo definitions and shares passthrough parameter analysis.
reflex/app.py Caches connection metadata and reduces scheduling overhead for outgoing updates.

Reviews (2): Last reviewed commit: "Add changelog fragment" | Re-trigger Greptile

Comment thread reflex/state.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 483d2d1253

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread reflex/state.py
for substate_class in cls.get_substates():
substate_class.vars.setdefault(name, var)
# inherited_vars may alias this class's vars dict.
substate_class._skip_var_names = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recursively invalidate skip-var caches for dynamic vars

When add_var() is called on a state that already has nested substates, this clears the cache only for immediate children. Grandchildren have their inherited_vars updated indirectly because it aliases the child vars dict, but their _skip_var_names frozenset was already populated during class creation and remains stale, so Grandchild.get_skip_vars() omits the newly inherited dynamic var. Please invalidate descendants as well when propagating a dynamically added var.

Useful? React with 👍 / 👎.

claude added 3 commits July 17, 2026 18:44
_get_attribute was 43% of profiled event-handling time; much of it was
the framework's own reads of dirty_vars, parent_state, substates, and
_backend_vars paying the full inherited-vars/event-handler/proxying
chain on every access.

- Add those names (and dirty_substates) to the CLASS_VAR_NAMES fast
  path; they are never state vars, never inherited, and never proxied.
- Check base_vars/backend_vars membership before is_mutable_type when
  deciding whether to proxy, so non-var values skip the type check.
- Cache get_skip_vars() per class (invalidated when inherited_vars
  changes) instead of rebuilding the set on every internal write.
- Cache the frontend computed var names used by get_delta per class
  (invalidated when computed_vars changes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
@Alek99
Alek99 force-pushed the claude/reflex-perf-optimizations-01l7a3-eng-10096 branch from 97e9e37 to 1ded8bd Compare July 18, 2026 01:46
@Alek99
Alek99 changed the base branch from main to agent/event-loop-performance-benchmarks July 18, 2026 01:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants